package com.common.util.javers;

import org.javers.common.collections.Function;
import org.javers.common.collections.Lists;
import org.javers.common.collections.Optional;
import org.javers.common.collections.Predicate;
import org.javers.common.validation.Validate;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitId;
import org.javers.core.json.JsonConverter;
import org.javers.core.metamodel.object.CdoSnapshot;
import org.javers.core.metamodel.object.GlobalId;
import org.javers.core.metamodel.object.ValueObjectId;
import org.javers.core.metamodel.type.EntityType;
import org.javers.core.metamodel.type.ManagedType;
import org.javers.repository.api.JaversRepository;
import org.javers.repository.api.QueryParams;
import org.javers.repository.api.QueryParamsBuilder;
import org.javers.repository.api.SnapshotIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static java.util.Collections.unmodifiableList;

/**
 * 扩展
 * 每次功能编辑完后 可清空commit历史。
 */
public class InMemoryRepository implements JaversRepository {
    //////////////// 原实现部分
    private static final Logger logger = LoggerFactory.getLogger(InMemoryRepository.class);
    private Map<GlobalId, LinkedList<CdoSnapshot>> snapshots = new ConcurrentHashMap();
    private CommitId head;

    public InMemoryRepository() {
    }

    public List<CdoSnapshot> getValueObjectStateHistory(final EntityType ownerEntity, final String path, QueryParams queryParams) {
        Validate.argumentsAreNotNull(new Object[]{ownerEntity, path});
        List result = Lists.positiveFilter(this.getAll(), new Predicate() {
            @Override
            public boolean apply(Object o) {
                CdoSnapshot input=(CdoSnapshot)o;
                if(!(input.getGlobalId() instanceof ValueObjectId)) {
                    return false;
                } else {
                    ValueObjectId id = (ValueObjectId)input.getGlobalId();
                    return id.hasOwnerOfType(ownerEntity) && id.getFragment().equals(path);
                }
            }
        });
        return this.applyQueryParams(result, queryParams);
    }

    public List<CdoSnapshot> getStateHistory(GlobalId globalId, QueryParams queryParams) {
        Validate.argumentIsNotNull(globalId);
        return this.snapshots.containsKey(globalId)?Collections.unmodifiableList(this.applyQueryParams((List)this.snapshots.get(globalId), queryParams)):Collections.emptyList();
    }

    public List<CdoSnapshot> getStateHistory(ManagedType givenClass, QueryParams queryParams) {
        Validate.argumentIsNotNull(givenClass);
        ArrayList filtered = new ArrayList();
        Iterator var4 = this.getAll().iterator();

        while(var4.hasNext()) {
            CdoSnapshot snapshot = (CdoSnapshot)var4.next();
            if(snapshot.getGlobalId().isTypeOf(givenClass)) {
                filtered.add(snapshot);
            }
        }

        return this.applyQueryParams(filtered, queryParams);
    }

    public List<CdoSnapshot> getPropertyStateHistory(GlobalId globalId, String propertyName, QueryParams queryParams) {
        Validate.argumentsAreNotNull(new Object[]{globalId, propertyName});
        if(this.snapshots.containsKey(globalId)) {
            List filtered = this.filterByPropertyName((List)this.snapshots.get(globalId), propertyName);
            return Collections.unmodifiableList(this.applyQueryParams(filtered, queryParams));
        } else {
            return Collections.emptyList();
        }
    }

    public List<CdoSnapshot> getPropertyStateHistory(ManagedType givenClass, String propertyName, QueryParams queryParams) {
        Validate.argumentsAreNotNull(new Object[]{givenClass, propertyName});
        QueryParams increasedLimitQueryParams = this.getQueryParamsWithIncreasedLimit(queryParams);
        List filtered = this.filterByPropertyName(this.getStateHistory(givenClass, increasedLimitQueryParams), propertyName);
        return Collections.unmodifiableList(this.applyQueryParams(filtered, queryParams));
    }

    private QueryParams getQueryParamsWithIncreasedLimit(QueryParams queryParams) {
        return QueryParamsBuilder.initializeWith(queryParams).limit(queryParams.limit() * 10).build();
    }

    private List<CdoSnapshot> applyQueryParams(List<CdoSnapshot> snapshots, QueryParams queryParams) {
        if(queryParams.commitId().isPresent()) {
            snapshots = this.filterSnapshotsByCommitId(snapshots, (CommitId)queryParams.commitId().get());
        }

        if(queryParams.version().isPresent()) {
            snapshots = this.filterSnapshotsByVersion(snapshots, (Long)queryParams.version().get());
        }

        if(queryParams.hasDates()) {
            snapshots = this.filterSnapshotsByCommitDate(snapshots, queryParams);
        }

        return this.trimResultsToRequestedSlice(snapshots, queryParams.skip(), queryParams.limit());
    }

    private List<CdoSnapshot> filterSnapshotsByCommitId(List<CdoSnapshot> snapshots, final CommitId commitId) {
        return Lists.positiveFilter(snapshots, new Predicate() {
            @Override
            public boolean apply(Object o) {
                return commitId.equals(((CdoSnapshot)o).getCommitId());
            }
        });
    }

    private List<CdoSnapshot> filterSnapshotsByVersion(List<CdoSnapshot> snapshots, final Long version) {
        return Lists.positiveFilter(snapshots, new Predicate() {
            @Override
            public boolean apply(Object o) {
                return version.longValue() == ((CdoSnapshot) o).getVersion();
            }
        });
    }

    private List<CdoSnapshot> filterSnapshotsByCommitDate(List<CdoSnapshot> snapshots, final QueryParams queryParams) {
        return Lists.positiveFilter(snapshots, new Predicate() {
            @Override
            public boolean apply(Object o) {
                return false;
            }

            public boolean apply(CdoSnapshot snapshot) {
                return queryParams.isDateInRange(snapshot.getCommitMetadata().getCommitDate());
            }
        });
    }

    private List<CdoSnapshot> trimResultsToRequestedSlice(List<CdoSnapshot> snapshots, int from, int size) {
        int fromIndex = Math.min(from, snapshots.size());
        int toIndex = Math.min(from + size, snapshots.size());
        return snapshots.subList(fromIndex, toIndex);
    }

    public Optional<CdoSnapshot> getLatest(GlobalId globalId) {
        Validate.argumentsAreNotNull(new Object[]{globalId});
        if(this.snapshots.containsKey(globalId)) {
            LinkedList states = (LinkedList)this.snapshots.get(globalId);
            return Optional.of((CdoSnapshot) states.peek());
        } else {
            return Optional.empty();
        }
    }

    public List<CdoSnapshot> getSnapshots(Collection<SnapshotIdentifier> snapshotIdentifiers) {
        List persistedIdentifiers = this.getPersistedIdentifiers(snapshotIdentifiers);
        return Lists.transform(persistedIdentifiers, new Function() {
            @Override
            public Object apply(Object o) {
                SnapshotIdentifier snapshotIdentifier=(SnapshotIdentifier)o;
                List objectSnapshots = (List)InMemoryRepository.this.snapshots.get(snapshotIdentifier.getGlobalId());
                return (CdoSnapshot)objectSnapshots.get(objectSnapshots.size() - (int)snapshotIdentifier.getVersion());
            }
        });
    }

    private List<SnapshotIdentifier> getPersistedIdentifiers(Collection<SnapshotIdentifier> snapshotIdentifiers) {
        return Lists.positiveFilter(new ArrayList(snapshotIdentifiers), new Predicate() {

            @Override
            public boolean apply(Object o) {
                SnapshotIdentifier snapshotIdentifier=(SnapshotIdentifier)o;
                return InMemoryRepository.this.snapshots.containsKey(snapshotIdentifier.getGlobalId()) && snapshotIdentifier.getVersion() <= (long)((LinkedList)InMemoryRepository.this.snapshots.get(snapshotIdentifier.getGlobalId())).size();
            }
        });
    }

    public void persist(Commit commit) {
        Validate.argumentsAreNotNull(new Object[]{commit});
        List snapshots = commit.getSnapshots();
        Iterator var3 = snapshots.iterator();

        while(var3.hasNext()) {
            CdoSnapshot s = (CdoSnapshot)var3.next();
            this.persist(s);
        }

        logger.debug("{} snapshot(s) persisted", Integer.valueOf(snapshots.size()));
        this.head = commit.getId();
    }

    public CommitId getHeadId() {
        return this.head;
    }

    public void setJsonConverter(JsonConverter jsonConverter) {
    }

    private List<CdoSnapshot> filterByPropertyName(List<CdoSnapshot> snapshots, final String propertyName) {
        return Lists.positiveFilter(snapshots, new Predicate() {
            @Override
            public boolean apply(Object o) {
                CdoSnapshot input=(CdoSnapshot)o;
                return input.hasChangeAt(propertyName);
            }
        });
    }

    private List<CdoSnapshot> getAll() {
        ArrayList all = new ArrayList();
        Iterator var2 = this.snapshots.values().iterator();

        while(var2.hasNext()) {
            LinkedList snapshotsList = (LinkedList)var2.next();
            all.addAll(snapshotsList);
        }

        Collections.sort(all, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                CdoSnapshot o=(CdoSnapshot)o1;
                CdoSnapshot oo=(CdoSnapshot)o2;
                return oo.getCommitId().compareTo(o.getCommitId());
            }
        });
        return all;
    }

    private synchronized void persist(CdoSnapshot snapshot) {
        LinkedList states = (LinkedList)this.snapshots.get(snapshot.getGlobalId());
        if(states == null) {
            states = new LinkedList();
            this.snapshots.put(snapshot.getGlobalId(), states);
        }

        states.push(snapshot);
    }

    public void ensureSchema() {
    }

    //////////////// 原实现部分


    //#####################　　　新增部分　

    /**
     * 删除历史
     */
    public synchronized void commitDelete(GlobalId globalId){
        LinkedList<CdoSnapshot> all = snapshots.get(globalId);
        //为只记录每次编辑的更新，(一次编辑可能有多次更新),每次更新完 需手动
        //调用该方法
        //从最前一个开始删除 只留下一个 为后面做对比
        while (all!=null && all.size()>1){
            Object x=all.pollLast();
        }
    }


}
